Java基础知识(十一)
继承性
继承性的作用是解决代码重用问题。
继承问题的引出
范例:定义两个类Person和Student
class Person{
private String name;
private int age;
public void setName(String name){
this.name = name;
}
public void setAge(int age){
this.age = age;
}
public String getName(){
return this.name;
}
public int getAge(){
return this.age;
}
}
class Student{
private String name;
private int age;
private String school;
public void setName(String name){
this.name = name;
}
public void setAge(int age){
this.age = age;
}
public void setSchool(String school){
this.school = school;
}
public String getName(){
return this.name;
}
public int getAge(){
return this.age;
}
public String getSchool(){
return this.school;
}
}
由代码可见Studen和Person存在代码重复。在自然关系上,Student是Person的一种,只是Student描述的更细致,范围更小。
实现继承
继承使用关键字extends
实现,语法如下:class 子类 extends 父类{}
子类也被称为派生类
,父类也被称为基类、超类或super类
。
范例:实现继承
class Person { // 父类
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
class Student extends Person { // 继承Person类
}
public class Demo {
public static void main(String[] args) {
Student stu = new Student();
stu.setName("张三");
stu.setAge(18);
System.out.println("姓名:" + stu.getName() + ",年龄:" + stu.getAge());
}
}
Student继承了Person,可以使用Person类中的方法。
范例:在Student中添加属性和方法
class Student extends Person { // 继承Person类
private String school;
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
}
由上述代码,可知继承性的优点:
(1)子类可以直接使用父类的属性和方法,进行代码重用;
(2)子类可以扩充属于自己的操作。
继承的限制
Java中继承存在如下限制:
1、Java不允许多重继承,但允许多层继承。
C++允许多继承,即一个子类可以同时继承多个父类。但该操作在Java中是不允许的。多继承是为了使子类可以同时拥有多个父类的操作。Java中使用多层继承替代,语法如下:
class A{}
class B extends A{}
class C extends B{}
相当于C是B的子类,是A的孙子类。多层继承没有层数限制,但最好不超过三层。
2、子类继承父类时,会继承父类全部操作。对于私有操作属于隐式继承,对于非私有操作属于显式继承。
class A {
private String msg;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
class B extends A {
}
public class Demo {
public static void main(String[] args) {
B b = new B();
b.setMsg("Hello");
System.out.println(b.getMsg()); // Hello
}
}
上述代码显示B类中也存在属性msg,因为如果msg不存在,setMsg()设置的内容就不能保存,即getMsg()无法输出内容。
class B extends A {
public void fun() {
System.out.println(msg); // 报错,无法访问
}
}
但是在B类中无法直接访问msg,因为msg是A类的私有属性,只能间接访问。
3、在实例化子类对象之前,会先调用父类构造方法(默认是无参构造方法),以保证父类对象先实例化,而后在实例化子类对象。
class A {
public A() {
System.out.println("A 构造方法");
}
}
class B extends A {
public B() {
System.out.println("B 构造方法");
}
}
public class Demo {
public static void main(String[] args) {
B b = new B();
// A 构造方法
// B 构造方法
}
}
由结果可知,在实例化子类对象前,会先实例化父类对象。对于子类构造方法来说相当于隐藏一个”super()”.
class B extends A {
public B() {
super(); // 父类有无参构造方法时,加不加都一样
System.out.println("B 构造方法");
}
}
何时要在子类构造方法中添加super():如果父类中没有无参构造方法,就必须使用super调用父类的有参构造方法。
class A {
public A(String title) {
System.out.println("A 构造方法");
}
}
class B extends A {
public B() {
// 子类默认调用无参构造,但A中没有无参构造
System.out.println("B 构造方法");
}
}
上述代码执行后,不会调用A中的有参构造,因此需要在B类中的构造方法添加super():
class A {
public A(String title) {
System.out.println("A 构造方法");
}
}
class B extends A {
public B(String title) {
super(title);
System.out.println("B 构造方法");
}
}
super()
必须放在子类构造方法的第一行。而this()
也应该放在构造方法的首行。
问题:子类构造方法未添加super(),系统默认使用super()调用父类的无参构造方法。如果在子类构造方法中添加this(),那么子类是不是无法调用父类构造方法?
class B extends A {
public B() { // 报错,构造递归调用
this();
System.out.println("B 构造方法");
}
}
由结果可知,super()和this()不能同时存在。不论子类怎么修改,子类构造方法执行前都必须先执行父类的构造方法。
方法覆写
继承性的特点是子类可以对父类已有的功能进行扩展。子类在定义属性或方法时有可能与父类重名,该操作就称为覆写。
1、方法覆写:子类定义一个与父类方法的方法名、参数类型及个数、返回值都相同的方法。
class A {
public void fun() {
System.out.println("A中的方法");
}
}
class B extends A {
}
public class Demo {
public static void main(String[] args) {
B b = new B();
b.fun(); // A中的方法
}
}
此时B中没有fun(),所以调用的是从A继承的fun().
范例:方法覆写
class A {
public void fun() {
System.out.println("A中的方法");
}
}
class B extends A {
public void fun(){ // 方法覆写
System.out.println("覆写的方法");
}
}
public class Demo {
public static void main(String[] args) {
B b = new B();
b.fun(); // 覆写的方法
}
}
当方法覆写后,此时会调用子类中覆写的方法。
2、覆写结果的分析要素:
(1)实例化的是那个类;
(2)该对象调用的方法是否被覆写,如果未覆写将调用父类中的方法。
class B extends A {
public String fun(){
System.out.println("覆写的方法");
// 报错,B中fun()无法覆盖A中fun(),返回类型不兼容
return "Hello";
}
}
进行方法覆写时,不能改变方法中的返回值和参数个数。
3、方法覆写的使用原则:父类方法不能满足子类需求,但又必须使用该方法名时,要进行方法覆写。
方法覆写时还要考虑到权限问题,被子类覆写的方法不能拥有比父类更高的访问控制权限。
访问控制权限:public>default>private,private的访问权限最严格。即如果父类方法使用public方法,子类覆写此方法时,只能使用public。父类使用的是default,子类覆写时,只能用default或public.
范例:正确覆写
class A {
void fun() {
System.out.println("A中的方法");
}
}
class B extends A {
public void fun(){
System.out.println("覆写的方法");
}
}
错误覆写:
class A {
public void fun() {
System.out.println("A中的方法");
}
}
class B extends A {
void fun(){
System.out.println("覆写的方法");
// 报错,正在尝试分配更低权限。
}
}
上述代码中子类使用default,比public权限更严格,不符合方法覆写原则。
问题:父类方法使用private声明,子类使用public声明该方法,是覆写吗?
答:从概念上,private声明权限高于public,因此从权限上而言符合覆写的要求。观察下述代码:
class A {
public void fun() {
print();
}
private void print(){
System.out.println("Hello");
}
}
class B extends A {
public void print(){
System.out.println("World");
}
}
public class Demo {
public static void main(String[] args) {
B b = new B();
b.fun(); // Hello
}
}
从上述代码来看,子类并没有覆写print(),因为使用private定义的方法对于子类而言是不可见,因此子类定义的print()虽然符合覆写的要求,但是实际只是相当于定义了一个全新的方法,而不是方法覆写。而正确的覆写结果应该如下:
class A {
public void fun() {
print();
}
public void print(){
System.out.println("Hello");
}
}
class B extends A {
public void print(){
System.out.println("World");
}
}
public class Demo {
public static void main(String[] args) {
B b = new B();
b.fun(); // World
}
}
5、默认情况下,子类对象调用是一定是覆写后的方法。
class A {
public void print(){
System.out.println("Hello");
}
}
class B extends A {
public void print(){
print(); // 等同于this.print()
System.out.println("World");
}
}
public class Demo {
public static void main(String[] args) {
B b = new B();
b.print(); // 报错,方法递归调用,死循环
}
}
上述代码中,B类会优先调用B中print(),因此发生了递归调用。如果B中没有print(),则会调用父类中的。
范例:调用父类中方法super.方法名()
class B extends A {
public void print(){
super.print();
System.out.println("World");
}
}
super.方法名()与this.方法名()的区别:
(1)this.方法名()会优先查找本类中是否有目标方法,如果有则直接调用,没有就继续在父类中查找。
(2)super.方法名()会直接在父类中查找目标方法,不会在子类中查找。
问题:请说明重载(overloading)和覆写(override)的区别
No. | 区别 | 重载 | 覆写 |
---|---|---|---|
1 | 英文单词 | Overloading | Overrid |
2 | 发生范围 | 发生在一个类中 | 发生在继承关系中 |
3 | 定义 | 方法名相同,参数类型及个数不相同 | 方法名称、参数类型及个数,方法返回值都相同 |
4 | 权限 | 没有权限限制 | 被覆写的方法不能拥有比父类更严格的权限 |
在方法重载时,返回值可以不同,但为了程序设计的统一性,应尽量保证返回值类型一致。
属性覆写
1、子类定义了与父类完全相同的属性名时,称为属性覆写。
class A {
String info = "Hello";
}
class B extends A {
String info = "World";
public void print(){
System.out.println(super.info); // 调用父类属性
System.out.println(this.info); // 调用本类属性
}
}
public class Demo {
public static void main(String[] args) {
B b = new B();
b.print();
}
}
由于在开发中,类的属性必须封装,而封装后,属性覆写就没有意义。因为父类定义的私有属性,子类不可见,因此不会互相影响。
问题:super和this的区别
No. | 区别 | this | super |
---|---|---|---|
1 | 功能 | 调用本类中的操作 | 子类调用父类中的操作 |
2 | 形式 | 先从本类查找目标操作,再从父类中查找 | 只查找父类 |
3 | 特殊 | 表示本类的当前对象 | super不能单独使用 |
在开发中,对于本类或父类的操作,最好加上this.或super.,这样便于代码调试。
继承综合实战:数组操作
要求:定义Array类,在类中可以进行整型数组的操作:由外部传入数组的数据,可以进行数据的保存和输出,并且在这个类上派生出两个子类:
(1)排序类:通过此类取得的数据可以进行排序;
(2)反转类:通过此类取得的数据采用倒序的方式输出。
开发时,先不考虑子类,先开发父类。
根据要求定义父类Array,实现其操作。
思路:开辟好数组后,根据索引,一一存放数据。
class Array {
private int data[]; // 数组
private int foot; // 脚标
// 开辟数组空间
public Array(int len) {
if (len > 0) {
this.data = new int[len];
} else { // 数组默认长度为1
this.data = new int[1];
}
}
// 为数组添加数据
public boolean add(int num) {
if (this.foot < this.a[this.foot++] = num; // 保存数据
return true;
}
return false;
}
// 取得数组内容
public int[] getData() {
return this.data;
}
}
public class Demo {
public static void main(String[] args) {
Array array = new Array(3);
System.out.println(array.add(10)); // true
System.out.println(array.add(20)); // true
System.out.println(array.add(30)); // true
// 超出数组长度,false
System.out.println(array.add(40));
int[] temp = array.getData();
for (int x = 0; x < temp.length; x++) {
System.out.println(temp[x]); // 10 20 30
}
}
}
####定义子类。
思路:将Array类getData()返回的结果进行排序输出即可,因此要覆写父类的方法。
// 定义一个排序数组的子类
class SortArray extends Array {
// Array中没有无参构造方法,
// 需要明确调用父类的有参构造方法
public SortArray(int len) {
super(len);
}
// Array的getData()无法排序,进行方法覆写
public int[] getData() {
// 调用类库中的方法排序
java.util.Arrays.sort(super.getData());
return super.getData();
}
}
public class Demo {
public static void main(String[] args) {
SortArray array = new SortArray(3);
System.out.println(array.add(20)); // true
System.out.println(array.add(30)); // true
System.out.println(array.add(10)); // true
// 超出数组长度,false
System.out.println(array.add(40));
int[] temp = array.getData();
for (int x = 0; x < temp.length; x++) {
System.out.println(temp[x]); // 10 20 30
}
}
}
####定义反转子类,也要保持客户端操作不变,因此要覆写父类的方法。
// 定义一个反转子类
class ReverseArray extends Array {
public ReverseArray(int len) {
super(len);
}
public int[] getData() {
int center = super.getData().length / 2;
int head = 0;
int tail = super.getData().length - 1;
for (int x = 0; x < center; x++) {
int temp = super.getData()[head];
super.getData()[head] = super.getData()[tail];
super.getData()[tail] = temp;
head++;
tail--;
}
return super.getData();
}
}
public class Demo {
public static void main(String[] args) {
ReverseArray array = new ReverseArray(3);
System.out.println(array.add(20)); // true
System.out.println(array.add(10)); // true
System.out.println(array.add(30)); // true
// 超出数组长度,false
System.out.println(array.add(40));
int[] temp = array.getData();
for (int x = 0; x < temp.length; x++) {
System.out.println(temp[x]); // 10 20 30
}
}
}
总结:
子类扩充方法时,尽量根据需求覆写父类方法,而不是直接定义新方法。
This blog is under a CC BY-NC-SA 3.0 Unported License
本文链接:http://yov.oschina.io/article/Java/Java Base/Java基础知识(十一)/